using UnityEngine;
using System.Collections;
using System.Collections.Generic;

using Game.Helper;
namespace Game.CellSimulator.Data
{
    /// <summary>
    /// 用于管理细胞数据的Table
    /// </summary>
	public class Table 
	{
        /// <summary>
        /// 细胞即将成为的状态
        /// </summary>
        enum CellWilling { WillDie, WillAlive, Keep }

        /// <summary>
        /// 细胞目前的状态
        /// </summary>
        enum CellType { Die, Alive }

        /// <summary>
        /// 标记存活与否的地图（用于快速获得对应点的细胞状态）
        /// </summary>
        BitArray m_cellsMap;                                    //后期可以考虑Hash存储地图？或者Dictionary

        /// <summary>
        /// 地图大小
        /// </summary>
        int m_size;

        /// <summary>
        /// 活跃的细胞点
        /// </summary>
        public HashSet<IntVector2> m_aliveCellsSet { get; private set; }

        /// <summary>
        /// 开始演化之后的每一步的Hash记录，每条记录代表历史数据
        /// </summary>
        private List<int> m_stepsHashHistory;

        /// <summary>
        /// 遍历多少个点后开始等待视图更新；
        /// </summary>
        int m_countToRereshView ;

        //--可供回调的回调函数
        System.Action<IntVector2> OnCellBorn;
        System.Action<IntVector2> OnCellDie;
        CallbackFunc OnUpdateEnd;

		//---------------------------------------------------------------------------
		//				Public area
		//---------------------------------------------------------------------------
		#region Public area

        /// <summary>初始化Cell Table，并设置对应的回调（删除细胞和显示细胞的操作）</summary>
        /// <param name="OnCellBorn">当某位置细胞诞生时回调</param>
        /// <param name="onCellDie">当某位置细胞死亡时回调</param>
        /// <param name="OnUpdateActionFinished">当Table的所有更新操作完成时回调</param>
        public Table(int length,
                    System.Action<IntVector2> OnCellBorn,
                    System.Action<IntVector2> onCellDie,
                    CallbackFunc OnUpdateActionFinished)
        {
            m_size = length;
            m_countToRereshView = length;

            m_cellsMap = new BitArray(length * length);
            m_aliveCellsSet = new HashSet<IntVector2>();

            //设置规则：周边几个细胞下，中间细胞会存活/死亡
            //Init rule:
            SetCellAliveRule(2, CellWilling.Keep);
            SetCellAliveRule(3, CellWilling.WillAlive);

            this.OnCellBorn = OnCellBorn;
            this.OnCellDie = onCellDie;
            this.OnUpdateEnd = OnUpdateActionFinished;
        }

        /// <summary>
        /// 初始化放置细胞
        /// </summary>
        public void InitCells(List<IntVector2> cellsPos)
        {
            for(int i =  0 ; i < cellsPos.Count;i++)
            {
                PutCellAt(cellsPos[i]);
            }
        }
        
        /// <summary>
        /// <para>Put on an alive cell at this posistion</para>
        /// <para>放置一个活跃的细胞在这个位置的数据层上，并返回是否成功放置</para>
        /// </summary>
        public bool PutCellAt(IntVector2 pos)
        {
            //位置合法且未为die则可以放置
            if (IsLegalPos(pos)
                && (GetCellMapStatus(pos) == CellType.Die))
            {
                SetCellMapToStatus(pos, CellType.Alive);
                return true;
            }
            
            //其他情况均为False
            return false;
            
        }

        /// <summary>
        /// 删除某个位置上的细胞点
        /// </summary>
        /// <param name="IsPlayerAction">是否是玩家的删除动作</param>
        public void RemoveCellAt(IntVector2 pos,bool IsPlayerAction = false)
        {
            
            if (IsLegalPos(pos))
            {
                SetCellMapToStatus(pos, CellType.Die);
            }
        }

        #region 临时数据
        /// <summary>
        /// 即将复活的细胞的位置列表
        /// </summary>
        List<IntVector2> willBringToHell = new List<IntVector2>();
        
        /// <summary>
        /// 即将死亡的细胞的位置列表
        /// </summary>
        List<IntVector2> willBringToAlive = new List<IntVector2>();

        /// <summary>
        /// 用于遍历时标记该点是否在本回合中访问过了
        /// </summary>
        BitArray hasTraveled;
        #endregion

        /// <summary>
        /// <para>Update all cells in map</para>
        /// <para>更新地图上所有的细胞状态（令之出生/死亡）</para>
        /// </summary>
        public IEnumerator UpdateCells()
        {
            //获得活跃数据，而后在map中遍历活跃数据的周边数据
            //--This will record those will alive/die on map
            willBringToAlive.Clear();
            willBringToHell.Clear();


            #region Round On,只负责计算哪些点需要进行更新/删除
            //用临时hash存储本轮遍历过的点
            //HashSet<Vector2> hashTraveled = new HashSet<Vector2>();
            if (hasTraveled == null)
                hasTraveled = new BitArray(m_size * m_size);
            else
                hasTraveled.SetAll(false);

            //遍历活跃数据，但不会立即更改棋盘状态
            foreach (IntVector2 alivePos in m_aliveCellsSet)
            {
                //--遍历活跃数据附近的点：
                for (int directionI = 0; directionI < directionVector.Length; directionI++)
                {
                    //--获得具体点，再在该点发散开去
                    IntVector2 currPos = alivePos + directionVector[directionI];

                    //--Illegal Pos or traveled position will be skiped.
                    if (!IsLegalPos(currPos)
                        || hasTraveled[currPos.x * m_size + currPos.y] == true )
                    {
                        continue;
                    }

                    //--Set traveled:
                    //hashTraveled.Add(currPos);
                    hasTraveled[currPos.x * m_size + currPos.y] = true;

                    //--Do update, let it die / appear in list
                    CellWilling willStatus;
                    DoUpdateCell(currPos, out willStatus);

                    //Check index & count
                    if (willStatus == CellWilling.WillDie)
                    {
                        willBringToHell.Add(currPos);
                        
                    }
                    else if (willStatus == CellWilling.WillAlive)
                    {
                        willBringToAlive.Add(currPos);
                        
                    }
                }
                

            }
            #endregion

            #region Round Over, 真正反应到棋盘上的数据

            //已经处理过的细胞数目
            int cellsHandled = 0;

            //--Push those will die to hell.
            for (int i = 0; i < willBringToHell.Count; i++)
            {
                SetCellMapToStatus(willBringToHell[i], CellType.Die);

                cellsHandled++;
                //--提前Yield，避免计算过久
                if (cellsHandled > m_countToRereshView)
                {
                    cellsHandled = 0;
                    yield return -1;
                }
            }

            //--Let those will alive alive
            for (int i = 0; i < willBringToAlive.Count; i++)
            {
                SetCellMapToStatus(willBringToAlive[i], CellType.Alive);

                cellsHandled++;
                //--提前Yield，避免计算过久
                if (cellsHandled > m_countToRereshView)
                {
                    cellsHandled = 0;
                    yield return -1;
                }
            }

            #endregion

            //Callback that I've finished updated.
            OnUpdateEnd();
        }

        /// <summary>
        /// 清除数据，并且向视图回调
        /// </summary>
        /// <remarks>
        /// 备注：
        /// 不能直接ClearPosAt，因为这里存活细胞用的是HashSet的Foreach，Clear后会删除HashSet导致索引出错
        /// </remarks>
        public void ClearAllCellDatas()
        {
            //向视图回调：
            foreach (IntVector2 pos in m_aliveCellsSet)
            {
                OnCellDie(pos);
                //Debug.Log("Clear pos" + pos);
            }

            //清理自己的数据：
            //--存活细胞的数据
            m_aliveCellsSet.Clear();
            //--地图数据全重置：
            m_cellsMap.SetAll(false);
        }



		#endregion

		//---------------------------------------------------------------------------
		//				Private area
		//---------------------------------------------------------------------------
		#region Private area

        /// <summary>
        /// 规则存储，第n点为true即为被n个包围的情况下可存活
        /// </summary>
        CellWilling[] m_cellAliveRule = new CellWilling[9];//0~8个

        /// <summary>
        /// 可遍历的方向向量
        /// </summary>
        IntVector2[] directionVector = new IntVector2[9]{
                new IntVector2(0,0),//自己也要带入计算！但计算周边数的时候不用
                new IntVector2(0,1),
                new IntVector2(0,-1),
                new IntVector2(1,0),
                new IntVector2(-1,0),
                new IntVector2(1,1),
                new IntVector2(1,-1),
                new IntVector2(-1,1),
                new IntVector2(-1,-1),
            };

        /// <summary>
        /// 判断该点位置是否合法
        /// </summary>
        private bool IsLegalPos(IntVector2 pos)
        {
            if (pos.x < 0
                || pos.y < 0
                || pos.x >= m_size
                || pos.y >= m_size)
            {
                return false;
            }
            else
            {
                return true;
            }
        }

        /// <summary>
        /// 计算Cell周边8个位置的活跃数据（包括Alive和WillDie）
        /// </summary>
        /// <param name="atPos"></param>
        /// <returns></returns>
        private int GetAliveCountAround(IntVector2 atPos)
        {
            int aliveCount = 0;

            IntVector2 currPos = new IntVector2();
            for (int i = 1; i < directionVector.Length; i++)            //从方向1开始，避免了计算自己的数目
            {
                currPos = atPos + directionVector[i];

                //If it is not a legal position, skip
                if (!IsLegalPos(currPos))
                {
                    continue;
                }

                //--Check if it exist:
                CellType currCellType = GetCellMapStatus(currPos);
                if (currCellType == CellType.Alive)
                {
                    aliveCount++;
                }
            }

            return aliveCount;
        }

        /// <summary>
        /// <para>Update this cell status</para>
        /// <para>通过获得周边的数据，获得这个细胞即将进入的状态（将活，还是死亡）</para>
        /// </summary>
        private void DoUpdateCell(IntVector2 currPos, out CellWilling willToStatus)
        {
            int surroundCount = GetAliveCountAround(currPos);
            willToStatus = CellWilling.Keep;

            CellType currCellStatus = GetCellMapStatus(currPos);
            CellWilling ruleStatus = IsCellAliveInRule(surroundCount);
            //Debug.Log("with" + surroundCount + " will " + ruleStatus);

            if (currCellStatus == CellType.Alive)
            {
                if (ruleStatus == CellWilling.WillDie)
                    willToStatus = CellWilling.WillDie;
            }
            else //if (currCellStatus == CellType.Die)
            {
                if (ruleStatus == CellWilling.WillAlive)
                    willToStatus = CellWilling.WillAlive;
            }
        }

        /// <summary>
        /// <para>设置地图上该点的存活状态</para>
        /// <para>Turn  the  status of cell on map to</para>
        /// </summary>
        private void SetCellMapToStatus(IntVector2 currPos, CellType toStatus)
        {
            //计算出该点在bitArray地图中的下标（用一维表示二维数组）
            int mapPosIndex = currPos.x * m_size + currPos.y;
            
            switch (toStatus)
            {
                case CellType.Alive:
                    if (m_aliveCellsSet.Add(currPos))
                    {
                        //真正添加成功了才向视图回调一次
                        OnCellBorn(currPos);

                        //Set true if alive
                        m_cellsMap[mapPosIndex] = true;
                    }
                    //else
                    //{
                    //    Debug.LogError("Cannot set alive at pos:" + currPos);
                    //}
                    break;

                case CellType.Die:
                    //同上
                    if (m_aliveCellsSet.Remove(currPos))
                    {
                        OnCellDie(currPos);

                        m_cellsMap[mapPosIndex] = false;
                    }
                    //else
                    //{
                    //    Debug.LogError("Cannot set cell dead at pos:" + currPos);
                    //}
                    break;
            }
            
        }

        /// <summary>
        /// <para>获得该点的细胞状态</para>
        /// <para>Get the status of this position on map</para>
        /// </summary>
        private CellType GetCellMapStatus(IntVector2 currPos)
        {
            int indexPos = currPos.x * m_size + currPos.y;

            //return alive if true:
            if (m_cellsMap[indexPos] == true)
                return CellType.Alive;
            else
                return CellType.Die;
        }

        /// <summary>
        /// 该点在被n个点包围的情况下，是否变成Alive状态
        /// </summary>
        /// <param name="surroundCount"></param>
        /// <returns></returns>
        private CellWilling IsCellAliveInRule(int surroundCount)
        {
            return m_cellAliveRule[surroundCount];
        }

        /// <summary>
        /// 设定规则，在N个点包围情况下将变成对应状态
        /// </summary>
        /// <param name="surroundCount"></param>
        private void SetCellAliveRule(int surroundCount, CellWilling alive)
        {
            m_cellAliveRule[surroundCount] = alive;
        }

        /// <summary>
        /// 当前的细胞图是否重现了历史的某一步
        /// </summary>
        private bool IsMapReapear()
        {
            //获得当前细胞图状态的hash，并进行比对。
            int currMapHash = m_aliveCellsSet.GetContentHash();
            //如果在历史步骤中出现过，则代表重现了历史的某一步，否则为false
            return m_stepsHashHistory.Contains(currMapHash);
        }
		#endregion

    }
}